#ifndef REAL_TIME_CLOCK_H
#define REAL_TIME_CLOCK_H

#include <Arduino.h>

typedef struct tm {
  int tm_sec;         /* seconds */
  int tm_min;         /* minutes */
  int tm_hour;        /* hours */
  int tm_mday;        /* day of the month */
  int tm_mon;         /* month */
  int tm_year;        /* year */
  int tm_wday;        /* day of the week */
  int tm_yday;        /* day in the year */
  int tm_isdst;       /* daylight saving time */
} tm;

#define DS1307_ADDRESS 0x68

// NOTE: sda -> analog4
//       scl -> analog5

class RTC {
 public:
  RTC() {
    Wire.begin();
  }

  byte bcdToDec(byte val)  {
    // Convert binary coded decimal to normal decimal numbers
    return ( (val/16*10) + (val%16) );
  }

  byte decToBcd(byte val){
  // Convert normal decimal numbers to binary coded decimal
    return ( (val/10*16) + (val%10) );
  }

  void getDateTime(tm *time) {
    Wire.beginTransmission(DS1307_ADDRESS);
    Wire.write((uint8_t) 0);
    Wire.endTransmission();

    Wire.requestFrom(DS1307_ADDRESS, 7);

    time->tm_sec = bcdToDec(Wire.read());
    time->tm_min = bcdToDec(Wire.read());
    time->tm_hour = bcdToDec(Wire.read() & 0b111111); //24 hour time
    time->tm_wday = bcdToDec(Wire.read()); //0-6 -> Sunday - Saturday
    time->tm_mday = bcdToDec(Wire.read());
    time->tm_mon = bcdToDec(Wire.read());
    time->tm_year = bcdToDec(Wire.read());

    static const int daysSinceJan1st[2][13]=
    {
      {0,31,59,90,120,151,181,212,243,273,304,334,365}, // 365 days, non-leap
      {0,31,60,91,121,152,182,213,244,274,305,335,366}  // 366 days, leap
    };

    int year = time->tm_year + 2000;
    int idx = (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0) ? 1 : 0);
    time->tm_yday = daysSinceJan1st[idx][time->tm_mon] + time->tm_mday;
  }

  unsigned long getTimeSinceEpoch() {
    tm now;
    getDateTime(&now);
    return getTimeSinceEpoch(now);
  }

  unsigned long getTimeSinceEpoch(const tm& now) {
    // tm_year is years since 2000, this formula expects years since 1900.
    unsigned long year = now.tm_year + 100;
    unsigned long time_since_epoch =
      now.tm_sec + now.tm_min*60 + (unsigned long) (now.tm_hour)*3600 +
      (unsigned long) (now.tm_yday)*86400 +
      (year-70)*31536000 + ((year-69)/4)*86400 -
      ((year-1)/100)*86400 + ((year+299)/400)*86400;
    return time_since_epoch;
  }

  void getDateTime(char buff[20]) {
    tm now;
    getDateTime(&now);
    int i = 0;
    buff[i++] = '2';
    buff[i++] = '0';
    buff[i++] = '0' + now.tm_year / 10;
    buff[i++] = '0' + now.tm_year % 10;
    buff[i++] = '-';
    buff[i++] = '0' + (now.tm_mon+1) / 10;
    buff[i++] = '0' + (now.tm_mon+1) % 10;
    buff[i++] = '-';
    buff[i++] = '0' + now.tm_mday / 10;
    buff[i++] = '0' + now.tm_mday % 10;
    buff[i++] = 'T';
    buff[i++] = '0' + now.tm_hour / 10;
    buff[i++] = '0' + now.tm_hour % 10;
    buff[i++] = ':';
    buff[i++] = '0' + now.tm_min / 10;
    buff[i++] = '0' + now.tm_min % 10;
    buff[i++] = ':';
    buff[i++] = '0' + now.tm_sec / 10;
    buff[i++] = '0' + now.tm_sec % 10;
    buff[i++] = '\0';
  }

  void setDateTime(tm& time) {
    // Sanity check:
    if (time.tm_sec < 0 || time.tm_sec > 60 ||
        time.tm_min < 0 || time.tm_min > 60 ||
        time.tm_hour < 0 || time.tm_hour > 24 ||
        time.tm_mday < 1 || time.tm_mday > 31 ||
        time.tm_mon < 0 || time.tm_mon > 11 ||
        time.tm_year < 0) {
      Serial.println("invalid time struct");
      return;
    }
    Wire.beginTransmission(DS1307_ADDRESS);
    Wire.write((uint8_t)0x00);
    Wire.write(decToBcd(time.tm_sec));  // 0 to bit 7 starts the clock
    Wire.write(decToBcd(time.tm_min));
    // If you want 12 hour am/pm you need to set
    // bit 6 (also need to change readDateDs1307)
    Wire.write(decToBcd(time.tm_hour));
    Wire.write(decToBcd(time.tm_wday));
    Wire.write(decToBcd(time.tm_mday));
    Wire.write(decToBcd(time.tm_mon));
    Wire.write(decToBcd(time.tm_year));

    Wire.write((uint8_t)0); //start
    Wire.endTransmission();
  }

  void setDateTime(char buff[20]) {
    // Sanity checks
    if (buff[19] != '\0' ||
        buff[16] != ':' ||
        buff[13] != ':' ||
        buff[10] != 'T' ||
        buff[7] != '-' ||
        buff[4] != '-') {
      Serial.print("invalid time format: ");
      Serial.println(buff);
      return;
    }

    tm new_now;
    new_now.tm_sec = buff[18] - '0';
    new_now.tm_sec += 10 * (buff[17] - '0');
    // ':'
    new_now.tm_min = buff[15] - '0';
    new_now.tm_min += 10 * (buff[14] - '0');
    // ':'
    new_now.tm_hour = buff[12] - '0';
    new_now.tm_hour += 10 *(buff[11] - '0');
    // 'T'
    new_now.tm_mday = buff[9] - '0';
    new_now.tm_mday += 10 * (buff[8] - '0');
    // '-'
    new_now.tm_mon = buff[6] - '0';
    new_now.tm_mon += 10 * (buff[5] - '0');
    // '-'
    new_now.tm_mon -= 1;
    new_now.tm_year = buff[3] - '0';
    new_now.tm_year += 10 * (buff[2] - '0');
    setDateTime(new_now);
  }

 private:
};

#endif // REAL_TIME_CLOCK_H
